package com.wechat.pay.api.service;

import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.api.config.PayUrlConfig;
import com.wechat.pay.api.config.WxConfig;
import com.wechat.pay.api.dao.UserDao;
import com.wechat.pay.api.exception.CustomException;
import com.wechat.pay.api.utils.HttpClientUtil;
import com.wechat.pay.api.vo.UserInfo;
import com.wechat.pay.api.vo.unifiedOrder.UnifiedOrderAmount;
import com.wechat.pay.api.vo.unifiedOrder.UnifiedOrderParam;
import com.wechat.pay.api.vo.unifiedOrder.UnifiedOrderPayer;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;

import javax.annotation.Resource;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;

/**
 * 拼接签名，正常情况下用wechatpay-apache-httpclient完全可以不用做签名和验签，
 * 如果要自己做签名的话有如下拼接签名的方式
 */
@Slf4j
@Service
public class WxPayServiceBuildSign {
    private static final String UNIFIED_ORDER_URL = "/v3/pay/transactions/jsapi";
    @Resource
    private WxConfig wxConfig;
    @Resource
    private UserDao userDao;
    @Resource
    private WxPayService payService;

    /**
     * 统一下单
     *
     * @param userId   用户id
     * @param payTotal 支付金额
     * @return 小程序调起微信支付控件的参数
     * @throws Exception 异常
     */
    public Map<String, String> unifiedOrder(Long userId, int payTotal) throws Exception {

        /*
         * 拼接签名的大概过程
         */
        // 构造签名串
        String nonceStr = RandomStringUtils.randomAlphanumeric(32);
        long timestamp = System.currentTimeMillis() / 1000;
        UnifiedOrderParam param = getUnifiedOrderParam(userId, payTotal, UUID.randomUUID().toString().replace("-", ""));
        String paramStr = JSONObject.toJSONString(param);
        //签名拼接
        String message = spliceNewline("POST", UNIFIED_ORDER_URL, timestamp, nonceStr, paramStr);
        // 生成token
        String token = token(wxConfig.getMchId(), nonceStr, timestamp, wxConfig.getSerialNo(), sign(message.getBytes(StandardCharsets.UTF_8)));
        // 请求头
        Map<String, Object> headers = new HashMap<>(1);
        headers.put("Authorization", token);


        String response = HttpClientUtil.doPost(PayUrlConfig.UNIFIED_ORDER, paramStr, headers);
        if ("请求失败".equals(response)) {
            throw new CustomException("请求失败");
        } else {
            JSONObject jsonObject = JSONObject.parseObject(response);
            String prepay_id = ((Map<String, Object>) jsonObject).get("prepay_id").toString();
            if ("".equals(prepay_id) || prepay_id == null) {
                throw new CustomException("prepay_id 为空");
            } else {
                //传给前端页面
                return getWxPayParam(prepay_id);
            }
        }
    }

    /**
     * 统一下单参数拼接
     *
     * @param userId   用户id
     * @param payTotal 支付金额
     * @param orderId  订单号
     * @return 统一下单参数
     */
    private UnifiedOrderParam getUnifiedOrderParam(Long userId, int payTotal, String orderId) {
        Optional<UserInfo> wsWxUserInfoOptional = userDao.queryByUserId(userId);

        if (!wsWxUserInfoOptional.isPresent()) {
            throw new CustomException("非法用户！");
        }
        UserInfo wsWxUserInfo = wsWxUserInfoOptional.get();
        // 订单金额
        UnifiedOrderAmount amount = UnifiedOrderAmount.builder().total(payTotal).currency("CNY").build();
        //支付者
        UnifiedOrderPayer payer = UnifiedOrderPayer.builder().openid(wsWxUserInfo.getOpenId()).build();
        return UnifiedOrderParam
                .builder()
                .appid(wxConfig.getAppId())
                .mchid(wxConfig.getMchId())
                .description("商品描述")
                .out_trade_no(orderId)
                .notify_url(wxConfig.getPayNotifyUrl())
                .amount(amount)
                .payer(payer)
                .build();
    }

    /**
     * 拼接小程序调起微信支付控件的参数
     *
     * @param prePayId 预支付编码
     * @return 参数
     * @throws Exception 异常
     */
    private Map<String, String> getWxPayParam(String prePayId) throws Exception {
        String nonceStr = RandomStringUtils.randomAlphanumeric(32);
        long timestamp = System.currentTimeMillis() / 1000;
        //包装prepay_id 等 返给前端由前端拉起支付
        HashMap<String, String> params = new HashMap<>();
        params.put("appId", wxConfig.getAppId());
        params.put("nonceStr", nonceStr);
        params.put("package", "prepay_id=" + prePayId);
        params.put("signType", "RSA");
        params.put("timeStamp", String.valueOf(timestamp));
        //重新签名
        String signMessage = spliceNewline(wxConfig.getAppId(), timestamp, nonceStr, "prepay_id=" + prePayId);
        String paySign = sign(signMessage.getBytes(StandardCharsets.UTF_8));
        params.put("paySign", paySign);
        return params;
    }

    /**
     * 支付回调（只有验签的逻辑其余和WxPayService中的逻辑一样）
     *
     * @param request
     * @throws Exception
     */
    public void wxNotify(HttpServletRequest request) throws Exception {
        String body = getRequestBody(request);
        boolean b = verifySign(request, body);
    }

    private boolean verifySign(HttpServletRequest request, String body) throws Exception {
        // 微信返回的证书序列号
        String serialNo = request.getHeader("Wechatpay-Serial");
        if (serialNo.equals(wxConfig.getSerialNo())) {
            log.info("序列号一致 serialNo：{}", serialNo);
        }
        // 微信返回的随机字符串
        String nonceStr = request.getHeader("Wechatpay-Nonce");
        // 微信返回的时间戳
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        // 微信返回的签名
        String wxSign = request.getHeader("Wechatpay-Signature");
        // 组装签名字符串
        String signStr = spliceNewline(timestamp, nonceStr, body);
        X509Certificate wechatpayCertificate = PemUtil.loadCertificate(new ByteArrayInputStream(getPublicKey().getBytes(StandardCharsets.UTF_8)));
        // SHA256withRSA签名
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(wechatpayCertificate);
        signature.update(signStr.getBytes());
        // 返回验签结果
        return signature.verify(Base64Utils.decodeFromString(wxSign));
    }

    /**
     * 定期更新平台证书（更新频率可以为每天更新或者每八小时/十二小时更新一次）
     *
     * @throws Exception 异常
     */
    @Scheduled(cron = "0 0/5 * * * ?")
    public void updatePlatformCertificateFile() throws Exception {
        File file = new File(wxConfig.getCertificateKeyPath());
        String certificateContent = readFileContent(file);
        String newCertificateContent = payService.getPlatformCertificateList();
        if (!certificateContent.equals(newCertificateContent)) {
            BufferedWriter sensitiveWordsWriter = new BufferedWriter(new OutputStreamWriter(
                    new FileOutputStream(file.getPath())));
            sensitiveWordsWriter.write(newCertificateContent);
            sensitiveWordsWriter.flush();
            sensitiveWordsWriter.close();
        }
    }

    /**
     * 解析文件内容
     *
     * @param file 文件
     * @return 文件内容
     * @throws Exception 异常
     */
    private String readFileContent(File file) throws Exception {
        StringBuilder sbf = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
            String tempStr;
            while ((tempStr = reader.readLine()) != null) {
                sbf.append(tempStr);
            }
            reader.close();
            return sbf.toString();
        }
    }

    /**
     * 解析request中的数据
     *
     * @param request HttpServletRequest
     * @return request中的数据
     */
    private String getRequestBody(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try (ServletInputStream stream = request.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (Exception e) {
            log.error("failed e={}", e.getMessage());
            throw new CustomException("操作失败");
        }
        return sb.toString();
    }

    /**
     * 拼接换行符
     *
     * @param param 参数
     * @return 拼接后的结果
     */
    private String spliceNewline(Object... param) {
        StringBuilder result = new StringBuilder();
        for (Object str : param) {
            result.append(str).append(StringUtils.LF);
        }
        return result.toString();
    }

    /**
     * @param mchId     商户号
     * @param nonceStr  随机字符串
     * @param timestamp 时间戳
     * @param serialNo  序列号
     * @param signature 签名
     * @return token
     */
    public String token(String mchId, String nonceStr, long timestamp, String serialNo, String signature) {
        final String TOKEN_PATTERN = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\","
                + "timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
        // 生成token
        return String.format(TOKEN_PATTERN, mchId, nonceStr, timestamp, serialNo, signature);
    }

    /**
     * 生成签名
     *
     * @param message 签名信息
     * @return 签名
     * @throws Exception 异常
     */
    private String sign(byte[] message) throws Exception {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey());
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }

    /**
     * 获取私钥。
     *
     * @return 私钥对象
     */
    public static PrivateKey getPrivateKey() throws IOException {
        ClassPathResource resource = new ClassPathResource("private_key.pem");
        String content = new String(Files.readAllBytes(Paths.get(resource.getPath())), StandardCharsets.UTF_8);
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", StringUtils.EMPTY)
                    .replaceAll("\\s+", StringUtils.EMPTY);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }

    /**
     * 获取平台公钥
     * （平台公钥要放在服务器某个路径下，因为平台证书会定期更新，
     * 如果放在resource下，打包之后可能更新不了）
     *
     * @return 平台公钥
     * @throws Exception 异常
     */
    private String getPublicKey() throws Exception {
        String certificateKey = readFileContent(new File(wxConfig.getCertificateKeyPath()));
        X509Certificate wechatpayCertificate = PemUtil.loadCertificate(new ByteArrayInputStream(certificateKey.getBytes(StandardCharsets.UTF_8)));
        return wechatpayCertificate.getPublicKey().toString();
    }
}